﻿
Namespace YahooManaged.Geo.PlaceFinder.API


    Public Class PlaceFinderDownload
        Inherits Base.Download

        Public Event AsyncDownloadCompleted(ByVal sender As Base.Download, ByVal e As PlaceFinderDownloadCompletedEventArgs)

        Public Property Options As PlaceFinderOptions = New PlaceFinderOptions


        Public Function Download(ByVal query As PlacesFinderQuery) As PlaceFinderResponse
            If query Is Nothing Then Throw New ArgumentNullException("query", "The PlaceFinderQuery is null")
            Return Me.ToResponse(MyBase.DownloadStream(Me.DownloadUrl(query)))
        End Function

        Public Sub DownloadAsync(ByVal query As PlacesFinderQuery, Optional ByVal userArgs As Object = Nothing)
            If query Is Nothing Then Throw New ArgumentNullException("query", "The PlaceFinderQuery is null")
            MyBase.DownloadStreamAsync(Me.DownloadUrl(query), New AsyncArgs(userArgs))
        End Sub

        Private Sub MyBase_DownloadCompleted(ByVal sender As Base.Download, ByVal e As Base.StreamDownloadCompletedEventArgs) Handles MyBase.AsyncStreamDownloadCompleted
            Dim args = CType(e.UserArgs, AsyncArgs)
            RaiseEvent AsyncDownloadCompleted(Me, New PlaceFinderDownloadCompletedEventArgs(args.UserArgs, Me.ToResponse(e.Response)))
        End Sub

        Private Function DownloadUrl(ByVal query As PlacesFinderQuery) As String
            If query Is Nothing Then Throw New ArgumentNullException("query", "The query options object is null")
            Dim url As New Text.StringBuilder
            With Me.Options
                url.Append("http://where.yahooapis.com/geocode?")
                If .Start > 0 And .Start <= 99 Then url.Append("&start=" & Me.Options.Start)
                If .Count <> 10 And .Count > 0 And .Count <= 100 Then url.Append("&count=" & .Count)
                If .Offset <> 15 And .Offset >= 0 And .Offset <= 100 Then url.Append("&offset=" & .Offset)

                Dim flags As New Text.StringBuilder
                If .CoordinatesOnly Then flags.Append("C")
                If .NoWOEID Then flags.Append("E")
                If .UsSpecificElements Then flags.Append("G")
                If .IncludeNearestAirportCode Then flags.Append("Q")
                If .IncludeTelephoneAreaCode Then flags.Append("R")
                If .IncludeDetailedStreetAttributes Then flags.Append("S")
                If .IncludeTimezoneInfo Then flags.Append("T")
                If .IncludeBoundingBox Then flags.Append("X")
                If flags.Length > 0 Then
                    url.Append("&flags=" & flags.ToString)
                End If
                Dim gflags As New Text.StringBuilder
                If .IncludeNeighborhoodNames Then gflags.Append("A")
                If .IncludeCrossStreets Then gflags.Append("C")
                If .LimitResultsToCulture Then gflags.Append("L")
                If .QuickMode Then gflags.Append("Q")
                If .ReverseGeoCoding Then gflags.Append("R")
                If gflags.Length > 0 Then
                    url.Append("&gflags=" & gflags.ToString)
                End If
                If .Culture IsNot Nothing AndAlso Not (.Culture.Language = Language.en And .Culture.Country = Country.US) Then
                    url.AppendFormat("&locale={0}_{1}", .Culture.Language, .Culture.Country)
                End If

                url.Append(query.QueryString)

            End With
            Return url.ToString
        End Function


        Private Function ToResponse(ByVal resp As Base.StreamResponse) As PlaceFinderResponse
            Dim results As New List(Of PlaceFinderData)
            Dim convCulture As New Globalization.CultureInfo("en-US")
            Dim conn As Base.ConnectionInfo = resp.Connection
            Dim errorCode As PlaceFinderError = PlaceFinderError.NoError
            Dim bestQuality As AddressQualitiy = AddressQualitiy.NotAnAddress

            Dim resultSet = mHelper.GetResultsFromXml(resp.Result, "ResultSet")
            If resultSet.Length = 1 Then
                Dim resultSetNode = resultSet(0)
                For Each resultSetElementNode As XmlNode In resultSetNode.ChildNodes

                    Select Case resultSetElementNode.Name
                        Case "Error" : errorCode = CType(CInt(resultSetElementNode.InnerText.Replace("NN", "")), PlaceFinderError)
                        Case "ErrorMessage"
                            If errorCode > PlaceFinderError.NoError Then
                                conn = New Base.ConnectionInfo(New Net.WebException("An internal Yahoo! error occured. See InnerException for more details.", New PlaceFinderException(errorCode, resultSetElementNode.InnerText)), resp.Connection.Timeout, resp.Connection.SizeInBytes, resp.Connection.StartTime, resp.Connection.EndTime)
                                Exit For
                            End If
                        Case "Locale"
                            Dim codes = resultSetElementNode.InnerText.Split(New String() {"_", "-"}, StringSplitOptions.None)
                            Dim language As Language = language.en
                            Dim country As Country = country.US
                            For Each lang As YahooManaged.Language In [Enum].GetValues(GetType(YahooManaged.Language))
                                If lang.ToString = codes(0) Then
                                    language = lang
                                    Exit For
                                End If
                            Next
                            For Each cnt As YahooManaged.Country In [Enum].GetValues(GetType(YahooManaged.Country))
                                If cnt.ToString = codes(1) Then
                                    country = cnt
                                    Exit For
                                End If
                            Next
                        Case "Quality" : bestQuality = CType(CInt(resultSetElementNode.InnerText), AddressQualitiy)
                        Case "Result"
                            Dim res As New PlaceFinderData
                            Dim lat, lon, latOff, lonOff As Double
                            For Each resultItemNode As XmlNode In resultSetElementNode.ChildNodes
                                Select Case resultItemNode.Name
                                    Case "quality" : res.Quality = CType(CInt(resultItemNode.InnerText), AddressQualitiy)
                                    Case "latitude" : Double.TryParse(resultItemNode.InnerText, Globalization.NumberStyles.Any, convCulture, lat)
                                    Case "longitude" : Double.TryParse(resultItemNode.InnerText, Globalization.NumberStyles.Any, convCulture, lon)
                                    Case "offsetlat" : Double.TryParse(resultItemNode.InnerText, Globalization.NumberStyles.Any, convCulture, latOff)
                                    Case "offsetlon" : Double.TryParse(resultItemNode.InnerText, Globalization.NumberStyles.Any, convCulture, lonOff)
                                    Case "radius" : Integer.TryParse(resultItemNode.InnerText, Globalization.NumberStyles.Any, convCulture, res.Radius)
                                    Case "boundingbox"
                                        Dim n, s, e, w As Double
                                        For Each bbItemNode As XmlNode In resultItemNode.ChildNodes
                                            Select Case bbItemNode.Name
                                                Case "north" : Double.TryParse(resultItemNode.InnerText, Globalization.NumberStyles.Any, convCulture, n)
                                                Case "south" : Double.TryParse(resultItemNode.InnerText, Globalization.NumberStyles.Any, convCulture, s)
                                                Case "east" : Double.TryParse(resultItemNode.InnerText, Globalization.NumberStyles.Any, convCulture, e)
                                                Case "west" : Double.TryParse(resultItemNode.InnerText, Globalization.NumberStyles.Any, convCulture, w)
                                            End Select
                                        Next
                                        Dim ne As New Coordinates(e, n)
                                        Dim sw As New Coordinates(w, s)
                                        res.BoundingBox = New CoordinatesRectangle(sw, ne)
                                    Case "name" : res.PoiAoiName = resultItemNode.InnerText
                                    Case "line1"
                                        If res.DefaultAddress Is Nothing Then res.DefaultAddress = New Address
                                        res.DefaultAddress.StreetAddressOrIntersection = resultItemNode.InnerText
                                    Case "line2"
                                        If res.DefaultAddress Is Nothing Then res.DefaultAddress = New Address
                                        res.DefaultAddress.CityOrStateOrZipCode = resultItemNode.InnerText
                                    Case ("line3")
                                        If res.DefaultAddress Is Nothing Then res.DefaultAddress = New Address
                                        res.DefaultAddress.PostalCode = resultItemNode.InnerText
                                    Case "line4"
                                        If res.DefaultAddress Is Nothing Then res.DefaultAddress = New Address
                                        res.DefaultAddress.Country = resultItemNode.InnerText
                                    Case "cross"
                                        If res.ExtendedAddress Is Nothing Then res.ExtendedAddress = New ExtendedAddress
                                        res.ExtendedAddress.CrossStreets = resultItemNode.InnerText
                                    Case "house"
                                        If res.ExtendedAddress Is Nothing Then res.ExtendedAddress = New ExtendedAddress
                                        res.ExtendedAddress.House = resultItemNode.InnerText
                                    Case "street"
                                        If res.ExtendedAddress Is Nothing Then res.ExtendedAddress = New ExtendedAddress
                                        If resultItemNode.HasChildNodes Then
                                            res.ExtendedAddress.Street = Me.GetStreetContainer(resultItemNode)
                                        Else
                                            res.ExtendedAddress.Street = New SimpleStreetDescription With {.FullName = resultItemNode.InnerText}
                                        End If
                                    Case "xstreet"
                                        If res.ExtendedAddress Is Nothing Then res.ExtendedAddress = New ExtendedAddress
                                        If resultItemNode.HasChildNodes Then
                                            res.ExtendedAddress.CrossStreet = Me.GetStreetContainer(resultItemNode)
                                        Else
                                            res.ExtendedAddress.CrossStreet = New SimpleStreetDescription With {.FullName = resultItemNode.InnerText}
                                        End If
                                    Case "unittype"
                                        If res.ExtendedAddress Is Nothing Then res.ExtendedAddress = New ExtendedAddress
                                        res.ExtendedAddress.UnitType = resultItemNode.InnerText
                                    Case "unit"
                                        If res.ExtendedAddress Is Nothing Then res.ExtendedAddress = New ExtendedAddress
                                        res.ExtendedAddress.Unit = resultItemNode.InnerText
                                    Case "postal"
                                        If res.ExtendedAddress Is Nothing Then res.ExtendedAddress = New ExtendedAddress
                                        res.ExtendedAddress.PostalCode = resultItemNode.InnerText
                                    Case "neighborhood"
                                        If res.ExtendedAddress Is Nothing Then res.ExtendedAddress = New ExtendedAddress
                                        res.ExtendedAddress.Neighborhoods = resultItemNode.InnerText.Split("/"c)
                                    Case "city"
                                        If res.ExtendedAddress Is Nothing Then res.ExtendedAddress = New ExtendedAddress
                                        res.ExtendedAddress.City = resultItemNode.InnerText
                                    Case "county"
                                        If res.ExtendedAddress Is Nothing Then res.ExtendedAddress = New ExtendedAddress
                                        res.ExtendedAddress.County = resultItemNode.InnerText
                                    Case "state"
                                        If res.ExtendedAddress Is Nothing Then res.ExtendedAddress = New ExtendedAddress
                                        res.ExtendedAddress.State = resultItemNode.InnerText
                                    Case "country"
                                        If res.ExtendedAddress Is Nothing Then res.ExtendedAddress = New ExtendedAddress
                                        res.ExtendedAddress.Country = resultItemNode.InnerText
                                    Case "level4"
                                        If res.ExtendedAddress Is Nothing Then res.ExtendedAddress = New ExtendedAddress
                                        res.ExtendedAddress.Neighborhoods = resultItemNode.InnerText.Split("/"c)
                                    Case "level3"
                                        If res.ExtendedAddress Is Nothing Then res.ExtendedAddress = New ExtendedAddress
                                        res.ExtendedAddress.City = resultItemNode.InnerText
                                    Case "level2"
                                        If res.ExtendedAddress Is Nothing Then res.ExtendedAddress = New ExtendedAddress
                                        res.ExtendedAddress.County = resultItemNode.InnerText
                                    Case "level1"
                                        If res.ExtendedAddress Is Nothing Then res.ExtendedAddress = New ExtendedAddress
                                        res.ExtendedAddress.State = resultItemNode.InnerText
                                    Case "level0"
                                        If res.ExtendedAddress Is Nothing Then res.ExtendedAddress = New ExtendedAddress
                                        res.ExtendedAddress.Country = resultItemNode.InnerText
                                    Case "countycode"
                                        If res.ExtendedAddress Is Nothing Then res.ExtendedAddress = New ExtendedAddress
                                        res.ExtendedAddress.CountyCode = resultItemNode.InnerText
                                    Case "statecode"
                                        If res.ExtendedAddress Is Nothing Then res.ExtendedAddress = New ExtendedAddress
                                        res.ExtendedAddress.StateCode = resultItemNode.InnerText
                                    Case "countrycode"
                                        If res.ExtendedAddress Is Nothing Then res.ExtendedAddress = New ExtendedAddress
                                        res.ExtendedAddress.CountryCode = resultItemNode.InnerText
                                    Case "level2code"
                                        If res.ExtendedAddress Is Nothing Then res.ExtendedAddress = New ExtendedAddress
                                        res.ExtendedAddress.CountyCode = resultItemNode.InnerText
                                    Case "level1code"
                                        If res.ExtendedAddress Is Nothing Then res.ExtendedAddress = New ExtendedAddress
                                        res.ExtendedAddress.StateCode = resultItemNode.InnerText
                                    Case "level0code"
                                        If res.ExtendedAddress Is Nothing Then res.ExtendedAddress = New ExtendedAddress
                                        res.ExtendedAddress.CountryCode = resultItemNode.InnerText
                                    Case "timezone"
                                        res.TimeZone = resultItemNode.InnerText
                                    Case "areacode"
                                        res.TelephoneAreaCode = resultItemNode.InnerText
                                    Case "uzip"
                                        res.UniqueZipCode = resultItemNode.InnerText
                                    Case "hash"
                                        res.Hash = resultItemNode.InnerText
                                    Case "woeid"
                                        Long.TryParse(resultItemNode.InnerText, Globalization.NumberStyles.Any, convCulture, res.WOEID)
                                    Case "woetype"
                                        Dim tInt As Integer = 0
                                        If Integer.TryParse(resultItemNode.InnerText, Globalization.NumberStyles.Any, convCulture, tInt) Then
                                            res.WOEType = CType(tInt, PlaceType)
                                        End If
                                End Select
                            Next
                            res.Position = New Coordinates(lon, lat)
                            res.PositionOffSet = New CoordinatesOffSet() With {.Latitude = latOff, .LongitudeOffSet = lonOff}
                            results.Add(res)
                    End Select
                Next
            End If
            Return New PlaceFinderResponse(conn, results.ToArray, bestQuality)
        End Function


        Private Function GetStreetContainer(ByVal node As XmlNode) As DetailedStreetDescription
            Dim res As DetailedStreetDescription = Nothing
            If node.Name = "street" Or node.Name = "xstreet" And node.HasChildNodes Then
                res = New DetailedStreetDescription
                For Each subNode As XmlNode In node.ChildNodes
                    Select Case subNode.Name
                        Case "stfull" : res.FullName = subNode.InnerText
                        Case "stpredir" : res.PrefixDirectional = subNode.InnerText
                        Case "stprefix" : res.PrefixType = subNode.InnerText
                        Case "stbody" : res.Body = subNode.InnerText
                        Case "stsuffix" : res.Suffix = subNode.InnerText
                        Case "stsufdir" : res.SuffixDirectional = subNode.InnerText
                    End Select
                Next
            End If
            Return res
        End Function

        Private Class AsyncArgs
            Inherits Base.DownloadEventArgs
            Public Sub New(ByVal userArgs As Object)
                MyBase.New(userArgs)
            End Sub
        End Class
    End Class

    Public Class PlaceFinderResponse
        Inherits Base.Response

        Private mBestQuality As AddressQualitiy
        Public Overloads ReadOnly Property Result As PlaceFinderData()
            Get
                Return CType(MyBase.Result, PlaceFinderData())
            End Get
        End Property
        Public ReadOnly Property BestQuality As AddressQualitiy
            Get
                Return mBestQuality
            End Get
        End Property

        Friend Sub New(ByVal conn As Base.ConnectionInfo, ByVal results() As PlaceFinderData, ByVal bestQual As AddressQualitiy)
            MyBase.New(conn, results)
            mBestQuality = bestQual
        End Sub

    End Class



    Public Class PlaceFinderDownloadCompletedEventArgs
        Inherits Base.DownloadCompletedEventArgs

        Public Overloads ReadOnly Property Response As PlaceFinderResponse
            Get
                Return CType(MyBase.Response, PlaceFinderResponse)
            End Get
        End Property

        Friend Sub New(ByVal userArgs As Object, ByVal resp As PlaceFinderResponse)
            MyBase.New(userArgs, resp)
        End Sub

    End Class


End Namespace
